#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "lcd_driver.h"
#include "NesCPU6502.h"
#include "NesSystem.H"
#include "NesPPU.h"


inline static void vNesPPUMemorySet(uint32_t addr, uint32_t value);
inline static uint32_t ucNesPPUMemoryGet(uint32_t addr);


// From http://nesdev.com/pal.txt
/* RGB565 */
const uint16_t cn_uxPaletteColorRGB[64] = {
0xAE73, 0xD120, 0x1500, 0x1340, 0x0E88, 0x02A8, 0x00A0, 0x4078, 0x6041, 0x2002, 0x8002, 0xE201, 0xEB19, 0x0000, 0x0000, 0x0000,
0xF7BD, 0x9D03, 0xDD21, 0x1E80, 0x17B8, 0x0BE0, 0x40D9, 0x61CA, 0x808B, 0xA004, 0x4005, 0x8704, 0x1104, 0x0000, 0x0000, 0x0000,
0xFFFF, 0xFF3D, 0xBF5C, 0x5FA4, 0xDFF3, 0xB6FB, 0xACFB, 0xC7FC, 0xE7F5, 0x8286, 0xE94E, 0xD35F, 0x5B07, 0x0000, 0x0000, 0x0000,
0xFFFF, 0x3FAF, 0xBFC6, 0x5FD6, 0x3FFE, 0x3BFE, 0xF6FD, 0xD5FE, 0x34FF, 0xF4E7, 0x97AF, 0xF9B7, 0xFE9F, 0x0000, 0x0000, 0x0000,
};

/* PPU CHR 存储区域 */
uint8_t st_ucPPUCHRRAM[0x1000 * 2];
/* Name Tables 1k * 4 名称表 */
uint8_t st_ucPPUNameTables[0x400 * 4];
/* 背景、精灵颜色表 */
uint8_t st_ucPPUColorTables[16 * 2];
/* OAM RAM 存储64个精灵数据 */
uint8_t st_ucPPUOAMRam[256] = {0};


NesPPUInofType st_typeNesPPUInfo = {0};


uint32_t cNesPPUBankAddr4kByteSet(uint32_t cSerial, uint32_t uiAddr)
{
    if(g_typeNesInfo.CHR_ROMSize == 0)
        return 1;

    if(cSerial == 0)
        st_typeNesPPUInfo.PatternTablesBank0 = &g_typeNesInfo.CHR_ROM[uiAddr % g_typeNesInfo.CHR_ROMSize];
    else
        st_typeNesPPUInfo.PatternTablesBank1 = &g_typeNesInfo.CHR_ROM[uiAddr % g_typeNesInfo.CHR_ROMSize];

    return 0;
}

uint32_t cNesPPUBankAddr8kByteSet(uint32_t uiAddr)
{
    uint32_t cError;

    cError  = cNesPPUBankAddr4kByteSet(0, uiAddr);
    cError |= cNesPPUBankAddr4kByteSet(1, uiAddr + 1024 * 4);

    return cError;
}

void vNesPPUMemoryLoad(uint32_t cSerial, uint32_t uiAddr, int32_t iLength)
{
    if(g_typeNesInfo.CHR_ROMSize == 0)
        return;

    /* 分成8块，每块1kBtye */
    if(((1024 * cSerial) + iLength) > 0x2000)
        return;

    /* 之所以用内存拷贝，是为了方便PPU代码画点时寻址，并且有些Mapper也需要把此图案区域当成可写入的状态 */
    memcpy(&st_ucPPUCHRRAM[1024 * cSerial], &g_typeNesInfo.CHR_ROM[uiAddr % g_typeNesInfo.CHR_ROMSize], iLength);
}

void vNesPPUOamRamSet(uint32_t ucAddr, uint8_t *pucBuff, uint32_t iLength)
{
    memcpy(&st_ucPPUOAMRam[ucAddr], pucBuff, iLength);
}

inline static uint32_t ucNesPPUMemoryGet(uint32_t addr)
{
    uint32_t value;

    /* 16k地址，后面进行镜像 */
    addr &= 0x3FFF;
    if(addr < 0x1000)
    {
        value = st_typeNesPPUInfo.PatternTablesBank0[addr];
    }
    else if(addr < 0x2000)
    {
        value = st_typeNesPPUInfo.PatternTablesBank1[addr & 0x0FFF];
    }
    else if(addr < 0x3F00)
    {
        /* 4k个Byte，后面进行镜像 */
        value = st_typeNesPPUInfo.NameTables[addr & 0x0FFF];
    }
    else
    {
        /* 32个Byte，后面进行镜像 */
        value = st_typeNesPPUInfo.ColorBackground[addr & 0x1F];
    }

    return value;
}

inline static void vNesPPUMemorySet(uint32_t addr, uint32_t value)
{
    /* 16k地址，后面进行镜像 */
    addr &= 0x3FFF;
    if(addr < 0x1000)
    {
        st_typeNesPPUInfo.PatternTablesBank0[addr] = value;
    }
    else if(addr < 0x2000)
    {
        st_typeNesPPUInfo.PatternTablesBank1[addr & 0x0FFF] = value;
    }
    else if(addr < 0x3F00)
    {
        /* 4k个Byte，后面进行镜像 */
        addr &= 0xFFF;

        /* 水平镜像（左右） */
        if(g_typeNesInfo.mirrorType == MIRROR_TYPE_HORIZONTAL)
        {
            st_typeNesPPUInfo.NameTables[addr | 0x400] = st_typeNesPPUInfo.NameTables[addr & ~0x400] = value;
        }
        /* 垂直镜像（上下） */
        else if(g_typeNesInfo.mirrorType == MIRROR_TYPE_VERTICAL)
        {
            st_typeNesPPUInfo.NameTables[addr | 0x800] = st_typeNesPPUInfo.NameTables[addr & ~0x800] = value;
        }
        /* single镜像 */
        else if(g_typeNesInfo.mirrorType < 4)
        {
            st_typeNesPPUInfo.NameTables[addr & ~0xC00] = st_typeNesPPUInfo.NameTables[addr | 0x400] = value;
            st_typeNesPPUInfo.NameTables[addr |  0x800] = st_typeNesPPUInfo.NameTables[addr | 0xC00] = value;
        }
        else
        {
            st_typeNesPPUInfo.NameTables[addr] = value;
        }
    }
    else
    {
        value &= 0x3F;
        if(addr & 0x03)
        {
            st_typeNesPPUInfo.ColorBackground[addr & 0x1F] = value;
        }
        else if((addr & 0x0F) == 0)
        {
            st_typeNesPPUInfo.ColorBackground[0x00] = st_typeNesPPUInfo.ColorSprite[0x00] = value;
            st_typeNesPPUInfo.ColorBackground[0x04] = st_typeNesPPUInfo.ColorSprite[0x04] = value;
            st_typeNesPPUInfo.ColorBackground[0x08] = st_typeNesPPUInfo.ColorSprite[0x08] = value;
            st_typeNesPPUInfo.ColorBackground[0x0C] = st_typeNesPPUInfo.ColorSprite[0x0C] = value;
        }
    }
}

uint32_t ucNesPPURegisterGet(uint32_t addr)
{
    static uint32_t st_ucValueLast = 0;
    uint32_t value = 0;

    /* 8个Byte，后面进行镜像 */
    switch(addr & 7)
    {
        case 2:
            value = st_typeNesPPUInfo.registers.PPUSTATUS;
            /* 被读取1次后，需要自动清除中断请求位 */
            st_typeNesPPUInfo.registers.PPUSTATUS &= ~0x80;
            st_typeNesPPUInfo.w = 0;
            break;

        case 4: value = *st_typeNesPPUInfo.OamAddr++; break;

        case 7:
            value = st_ucValueLast;
            /* 读取数据后，地址增加1或者增加32 */
            st_ucValueLast = (st_typeNesPPUInfo.registers.PPUADDR < 0x3F00) ? ucNesPPUMemoryGet(st_typeNesPPUInfo.registers.PPUADDR) : st_ucValueLast;
            st_typeNesPPUInfo.registers.PPUADDR += st_typeNesPPUInfo.addrIncrement;
            break;

        default : break;
    }

    return value;
}

void vNesPPURegisterSet(uint32_t addr, uint32_t value)
{
    /* 后面nByte是前面8Byte的镜像 */
    switch(addr & 7)
    {
        case 0:
            st_typeNesPPUInfo.registers.PPUCTRL = value;
            /* 内部寄存器T/V.nametable */
            st_typeNesPPUInfo.t.nametable = value & 0x03;
            /* 地址增加1或者增加32 */
            st_typeNesPPUInfo.addrIncrement = (value & 0x04) ? 32 : 1;
            break;

        case 1: st_typeNesPPUInfo.registers.PPUMASK = value; break;
        case 3: st_typeNesPPUInfo.OamAddr = &st_ucPPUOAMRam[value]; break;
        case 4: *st_typeNesPPUInfo.OamAddr++ = value; break;

        case 5:
            /* X scroll: 第1次为X轴偏移，第2次为Y轴偏移 */
            if(st_typeNesPPUInfo.w & 1)
                st_typeNesPPUInfo.t.fineY = value;
            else
                st_typeNesPPUInfo.t.fineX = value;

            ++st_typeNesPPUInfo.w;
            break;

        case 6:
            /* https://www.nesdev.org/wiki/PPU_scrolling */
            if(st_typeNesPPUInfo.w & 1)
            {
                st_typeNesPPUInfo.t.fineX = (st_typeNesPPUInfo.t.fineX & 0x07) | (value << 3);
                st_typeNesPPUInfo.t.fineY = (st_typeNesPPUInfo.t.fineY & 0x38) | (value & 0xE0) >> 2;

                /* PPU 内存地址: 第1次写入地址高字节，第2次写入地址低字节 */
                st_typeNesPPUInfo.registers.PPUADDR = (st_typeNesPPUInfo.registers.PPUADDR & 0xFF00) | value;
            }
            else
            {
                st_typeNesPPUInfo.t.nametable = (value >> 2) & 0x03;
                st_typeNesPPUInfo.t.fineY = (st_typeNesPPUInfo.t.fineY & 0x38) | ((value & 0x3F) >> 4) | (value << 6);

                st_typeNesPPUInfo.registers.PPUADDR = (st_typeNesPPUInfo.registers.PPUADDR & 0x00FF) | (value << 8);
            }

            ++st_typeNesPPUInfo.w;
            break;

        case 7:
            vNesPPUMemorySet(st_typeNesPPUInfo.registers.PPUADDR, value);
            st_typeNesPPUInfo.registers.PPUADDR += st_typeNesPPUInfo.addrIncrement;
            break;

        default : break;
    }
}

void vNesPPUStart(void)
{
    memset((uint8_t *)st_ucPPUOAMRam, 0xFF, sizeof(st_ucPPUOAMRam));
    memset((uint8_t *)st_ucPPUNameTables, 0xFF, sizeof(st_ucPPUNameTables));

    memset(&st_typeNesPPUInfo.t, 0, sizeof(st_typeNesPPUInfo.t));
    st_typeNesPPUInfo.PatternTablesBank0 = &st_ucPPUCHRRAM[0];
    st_typeNesPPUInfo.PatternTablesBank1 = &st_ucPPUCHRRAM[0x1000];
    st_typeNesPPUInfo.NameTables         = &st_ucPPUNameTables[0];
    st_typeNesPPUInfo.ColorBackground    = &st_ucPPUColorTables[0];
    st_typeNesPPUInfo.ColorSprite        = &st_ucPPUColorTables[0x10];
    st_typeNesPPUInfo.OamAddr            = &st_ucPPUOAMRam[0];

    st_typeNesPPUInfo.pointNowX = 0;
    st_typeNesPPUInfo.pointNowY = 0;
    st_typeNesPPUInfo.clockCnt = 0;
    st_typeNesPPUInfo.frameCnt = 0;
    st_typeNesPPUInfo.addrIncrement = 1;
}

void vNesPPUInit(void)
{
    memset(&st_typeNesPPUInfo, 0, sizeof(st_typeNesPPUInfo));
}

/* 扫描一行 背景 */
void vNesPPUDrawBackgroundLine(uint32_t uiPointYNow)
{
    /* 图案表的起始地址 */
    uint8_t *pucColorAddr, *pucPatternTableAddrBase = (st_typeNesPPUInfo.registers.PPUCTRL & 0x10) ? st_typeNesPPUInfo.PatternTablesBank1 : st_typeNesPPUInfo.PatternTablesBank0;
    uint32_t uiNametable = st_typeNesPPUInfo.t.nametable << 10, uiFineX = st_typeNesPPUInfo.t.fineX, uiFineY = st_typeNesPPUInfo.v.fineY;
    uint32_t uiCoarseY = uiFineY >> 3, uiFineYLow = uiFineY & 7, uiCoarseX, uiFineXEnd;
    uint32_t uiColorTile, uiColorHigh, uiColorLow;

    /* 每行有256个点 */
    uiPointYNow <<= 8;

    uiNametable = (uiNametable + ((uiFineY < 240) ? 0 : 2048)) & 4095;
    uiCoarseY  -= (uiCoarseY < 30) ? 0 : 30;

    /* 刷新本行256个点 */
    for(uiFineXEnd = uiFineX + 256; uiFineX < uiFineXEnd; uiFineX += 8 - (uiFineX & 7))
    {
        uiCoarseX = (uiFineX >> 3) & 31;

        if(uiFineX == 256)
        {
            uiNametable = (uiNametable + 1024) & 4095;
        }

        /* Tile 4 * 4 颜色序号 */
        uiColorTile = st_typeNesPPUInfo.NameTables[uiNametable + 960 + ((uiCoarseY >> 2) << 3) + (uiCoarseX >> 2)];

        /* 高两位颜色起始地址 */
        uiColorTile = ((uiColorTile >> (((uiCoarseX & 2) | ((uiCoarseY & 2) << 1)))) & 3) << 2;
        pucColorAddr = &st_typeNesPPUInfo.ColorBackground[uiColorTile];

        /* 名称表内图案的地址 */
        uiColorLow = st_typeNesPPUInfo.NameTables[uiNametable + (uiCoarseY << 5) + uiCoarseX];
        uiColorLow = (uiColorLow << 4) + uiFineYLow;

        /* 图案表内当前图块的图案 */
        uiColorHigh = pucPatternTableAddrBase[uiColorLow + 8];
        uiColorLow  = pucPatternTableAddrBase[uiColorLow];

        /* 渲染1个tile的1行 */
        for(uiCoarseX = 0x80 >> (uiFineX & 7); uiCoarseX; uiCoarseX >>= 1)
        {
            /* 低两位颜色序号 */
            uiColorTile = ((uiColorLow & uiCoarseX) != 0) | (((uiColorHigh & uiCoarseX) != 0) << 1);

            /* 打点显示 对应调色板中的颜色值 */
            LCD_VRAM_SET(uiPointYNow++, cn_uxPaletteColorRGB[pucColorAddr[uiColorTile]]);
        }
    }
}

/* 扫描一行 精灵 */
void vNesPPUDrawSpiritLine(uint32_t uiPointYNow)
{
    NesPPUSpiritInfoType *ptypeSpiritNow;
    uint8_t *pucColorAddr, *pucPatternTableAddrBase;
    uint32_t uiSpiritCnt = 0, uiSpiritColorLow, uiColorHigh, uiColorLow, uiFineXNow, uiCoordY, i;
    /* 8 * 8 精灵模式与 8 * 16 精灵模式 */
    uint32_t uiPointYDeltaMax = (st_typeNesPPUInfo.registers.PPUCTRL & 0x20) ? 16 : 8;
    uint8_t ucMask;

    /* 显示当前行的8个精灵，0号精灵最后绘制，以显示在最上面 */
    for(ptypeSpiritNow = (NesPPUSpiritInfoType *)&st_ucPPUOAMRam[0]; ptypeSpiritNow < (NesPPUSpiritInfoType *)&st_ucPPUOAMRam[256]; ++ptypeSpiritNow)
    {
        /* 本精灵不在当前行 */
        if((uiPointYNow < ptypeSpiritNow->coordY) || (uiPointYNow >= (ptypeSpiritNow->coordY + uiPointYDeltaMax)))
            continue;

        /* 精灵存储的Y地址+1 为实际地址Y */
        uiCoordY = ptypeSpiritNow->coordY + 1;

         /* 精灵溢出标志 */
        st_typeNesPPUInfo.registers.PPUSTATUS |= ((++uiSpiritCnt) > 8) ? 0x20 : 0;

        /* 精灵的行起始点坐标 */
        uiFineXNow = (uiPointYNow << 8) + ptypeSpiritNow->coordX;

        /* 0：前台；1：后台，并且背景像素为0 */
        if((ptypeSpiritNow->attribute & 0x20) && (ptypeSpiritNow != (NesPPUSpiritInfoType *)&st_ucPPUOAMRam[0]) && (LCD_VRAM_GET(uiFineXNow) != cn_uxPaletteColorRGB[st_typeNesPPUInfo.ColorBackground[0]]))
            continue;

        /* 8 * 16 模式 */
        if(st_typeNesPPUInfo.registers.PPUCTRL & 0x20)
        {
            /* title索引号，类似名称表 */
            pucPatternTableAddrBase  = (ptypeSpiritNow->titleNumber & 1) ? st_typeNesPPUInfo.PatternTablesBank1 : st_typeNesPPUInfo.PatternTablesBank0;
            pucPatternTableAddrBase += (ptypeSpiritNow->titleNumber & 0xFE) << 4;

            /* 是否垂直翻转，决定精灵的上半部分是否需要再偏移16个Byte */
            uiColorLow = (uiPointYNow - uiCoordY) < 8;
            pucPatternTableAddrBase += (ptypeSpiritNow->attribute & 0x80) ? (uiColorLow ? 16 : 0) : (uiColorLow ? 0 : 16);
        }
        /* 8 * 8 模式 */
        else
        {
            pucPatternTableAddrBase  = (st_typeNesPPUInfo.registers.PPUCTRL & 0x08) ? st_typeNesPPUInfo.PatternTablesBank1 : st_typeNesPPUInfo.PatternTablesBank0;
            pucPatternTableAddrBase += ptypeSpiritNow->titleNumber << 4;
        }

        /* 兼容 8 * 8 模式与 8 * 16 模式 */
        uiColorLow = (uiPointYNow - uiCoordY) & 7;
        uiColorLow = (ptypeSpiritNow->attribute & 0x80) ? (7 - uiColorLow) : uiColorLow;

        /* 地址偏移到当前tile的图案模型行起始地址 */
        uiColorHigh = pucPatternTableAddrBase[uiColorLow + 8];
        uiColorLow  = pucPatternTableAddrBase[uiColorLow];

        /* 高两位颜色起始地址 */
        pucColorAddr = &st_typeNesPPUInfo.ColorSprite[(ptypeSpiritNow->attribute & 0x03) << 2];

        /* tile 列、是否水平翻转 */
        for(ucMask = (ptypeSpiritNow->attribute & 0x40) ? 0x01 : 0x80; ucMask; ucMask = (ptypeSpiritNow->attribute & 0x40) ? (ucMask << 1) : (ucMask >> 1), ++uiFineXNow)
        {
            /* 获取颜色偏移的低两位 */
            uiSpiritColorLow = (((uiColorHigh & ucMask) != 0) << 1) | (((uiColorLow & ucMask) != 0) & 1);

            /* 精灵透明像素（0号像素），不覆盖背景 */
            if(uiSpiritColorLow != 0)
            {
                /* 精灵0击中检测 */
                if(((st_typeNesPPUInfo.registers.PPUSTATUS & 0x40) == 0) && (ptypeSpiritNow == (NesPPUSpiritInfoType *)&st_ucPPUOAMRam[0]))
                {
                    /* 获取屏幕上当前已经打印的背景点的颜色值 */
                    for(i = 1; i < 16; ++i)
                    {
                        if((i & 3) && (LCD_VRAM_GET(uiFineXNow) == cn_uxPaletteColorRGB[st_typeNesPPUInfo.ColorBackground[i]]))
                        {
                            st_typeNesPPUInfo.registers.PPUSTATUS |= 0x40;
                            break;
                        }
                    }
                }

                LCD_VRAM_SET(uiFineXNow, cn_uxPaletteColorRGB[pucColorAddr[uiSpiritColorLow]]);
            }
        }
    }
}

void vNesPPUExec(void)
{
    int32_t iTimeMsDeltal;

    /* 高于240行的不显示 */
    if(st_typeNesPPUInfo.pointNowY < 240)
    {
        /* 显示当前行背景 */
        if(st_typeNesPPUInfo.registers.PPUMASK & 0x08)
        {
            vNesPPUDrawBackgroundLine(st_typeNesPPUInfo.pointNowY);
        }

        /* 显示当前行精灵 */
        if(st_typeNesPPUInfo.registers.PPUMASK & 0x10)
        {
            vNesPPUDrawSpiritLine(st_typeNesPPUInfo.pointNowY);
        }

        ++st_typeNesPPUInfo.v.fineY;
    }
    /* V break */
    else if(st_typeNesPPUInfo.pointNowY < 261)
    {
        if(st_typeNesPPUInfo.pointNowY == 241)
        {
            /* set VBlank flag， CPU开始更新显存 */
            st_typeNesPPUInfo.registers.PPUSTATUS |= 0x80;

            /* 判断是否触发中断 */
            if(st_typeNesPPUInfo.registers.PPUCTRL & 0x80)
            {
                g_typeNesInfo.ptypeCPUInfo->NMIFlag = TRUE;
            }
        }
    }
    /* 261行 */
    else
    {
        st_typeNesPPUInfo.v = st_typeNesPPUInfo.t;
        st_typeNesPPUInfo.pointNowY = -1;

        /* clear VBlank flag， CPU结束更新显存 */
        st_typeNesPPUInfo.registers.PPUSTATUS &= ~0x80;
        /* 清除精灵0击中标志 */
        st_typeNesPPUInfo.registers.PPUSTATUS &= ~0x40;
        /* 清除精灵溢出标志 */
        st_typeNesPPUInfo.registers.PPUSTATUS &= ~0x20;

        ++st_typeNesPPUInfo.frameCnt;

        /* 计算已经超时多少us，并延时对应的时间，以稳定到60Hz刷新 */
        iTimeMsDeltal = (st_typeNesPPUInfo.frameCnt * (1000000.0 / 50.0)) - (lNesTimeGet() - g_typeNesInfo.timeStart);

        iTimeMsDeltal /= 1000;
        vNesDelayMs(iTimeMsDeltal);
    }

    ++st_typeNesPPUInfo.pointNowY;

    /* 刷新一行，需要341个时钟 */
    st_typeNesPPUInfo.clockCnt += 341;
}

NesPPUInofType *ptyeNesPPUInfoGet(void)
{
    return &st_typeNesPPUInfo;
}
